home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Almathera Ten Pack 3: CDPD 3
/
Almathera Ten on Ten - Disc 3: CDPD3.iso
/
fish
/
726-750
/
742
/
icalc
/
docs
/
advancedguide
next >
Wrap
Text File
|
1995-03-18
|
23KB
|
714 lines
=============================================================================
icalc - a complex-number expression language
(C) 1991, 1992 Martin W. Scott. All Rights Reserved.
Version 2.1
=============================================================================
Advanced Guide
=============================================================================
This document describes the more advanced features of icalc, including
programming constructs and array manipulation, as well as some information
that was left out of the User Guide for clarity and continuity.
The syntax used in icalc is closely related to that of C, with many
exceptions. Information/warnings specifically for people who are
familiar with C will be indicated by lines prefixed with 'C:'.
Parentheses and braces will sometimes be used in examples where they
don't strictly need to be, due to operator precedence rules. They are
included for clarity. (NB: A table detailing relative precedences is in
Appendix 1 of the User Guide.)
Numbers
-------
Icalc can read and display numbers in any base from 2 to 36. The most
common bases used are 10 (almost always), 2 (binary) and 16 (hexadecimal).
For bases greater than 10, the letters from A to Z (upper or lower-case)
are used for the 'digits' 11 to 35. If you don't know what a number-base
is, you may skip this section.
When you input a number, icalc assumes it is decimal unless indicated
otherwise. To inform icalc that you are entering a binary number, prefix
it with a percent sign '%'. Similarly, to denote a hexadecimal number,
use a '$' prefix. (Note that there should be no space between the prefix
and the number). For any other base, you should precede the number with
its base, a letter 'r' (in lower case) followed by the number itself.
Examples:
255 - (a) decimal representation of number 255
%11111111 - binary representation of number 255
$FF - hexadecimal representation of number 255
3r100110 - base-3 representation of number 255
36r73 - base-36 representation of number 255
Sometimes it is convenient to use scientific notation when representing
very large or small numbers. Usually, the letter 'e' denotes that an
exponent is given, as for example in 1e10 (a 1 followed by 10 zeros).
However, for bases >= 15, 'e' is a digit. I am unaware of any standard
convention in such cases, so opted to use the '@' character, which can be
used with any base (you may still use 'e' for any base < 15 though).
The exponent should be entered in the SAME BASE AS THE GIVEN NUMBER, and
icalc displays numbers using this convention also (this seems the
logical and consistent way to do things).
More Examples:
1e5 - (a) decimal representation of the number 10000
%101e11 - binary representation of the number 40
(%101 = 5, %11 = 3, so %101e11 = 5 * 2^3)
8r7e7 - octal representation of the number 14680064
(8r7 = 7, so 8r7e7 = 7 * 8^7)
$1@10 - hexadecimal representation of the number 16^16
When icalc displays a number using scientific notation, it always
explicitly shows the sign of its exponent, eg. entering 1e30 would
produce 1e+30.
Fractions are input in the usual way as for decimal numbers. Thus
$FF.FF
represents the number 15*16 + 15*1 + 15/16 + 15/(16^2), whilst
%101.101
represents the number 1*4 + 0*2 + 1*1 + 1/2 + 0/4 + 1/8.
Note that to convert from one base to another, it is not enough just to
convert each of the integer fraction and exponent parts separately. But
anyway, you can use icalc for all base conversions you might want.
To specify which base icalc should display numbers, use the builtin
outbase(n) - this affects all numbers output, except array indices output
with the display() builtin (see the section on arrays below).
The display routine in icalc prints a number to a certain number of
significant digits. This is 12 by default, but may be changed by the
prec() builtin. Entering prec(n) sets the number of significant digits
to n. n must lie in the range 1 to 55. prec() returns the last answer
(ans) so that you can see the effect immediately. It allows values up to
55 only for representations in lower bases -- you don't get 55 digits of
precision using decimal representations. To save time, the functions
bin(), oct(), dec() and hex() are defined in the 'icalc.init' file for
your use. They set respectively base 2, 8, 10 and 16, as well as setting
a suitable precision for numbers to be displayed at.
Generally speaking, icalc doesn't consider leading zeros in a fraction as
significant, e.g. in the number 0.0012, the significant part is 12. If a
fraction has more than the set number of significant digits, it is
displayed in scientific notation. Whilst this gives the desired number
of significant digits, it is sometimes aesthetically unpleasing.
Therefore, a fixed number of leading zeros are condidered significant,
by default 4. This can be altered by the sigzeros(n) builtin. Note that
if n is zero, numbers will always be displayed with the set number of
significant digits.
To demonstrate what I mean, here's an example (assuming default settings):
> PI
3.14159265359
> PI/10
0.31415926536
> PI/1000
0.00314159265
> PI/100000 # gives rise to 5 leading zeros
3.14159265359e-5
> sigzeros(5)
0.00003141593
^ ^^^^
these 5 zeros are considered significant
And finally, a small example on rounding errors. Recall from the user
guide the example expression
sin(2*x) - 2*sin(x)*cos(x)
where x had the value 4, and the expression returned 2.22044604925e-16.
This looks at first like a rather strange error, but turns out not to be.
The following sample session shows why:
> x = 4
4
> sin(2*x) - 2*sin(x)*cos(x)
2.22044604925e-16
> bin() # sets prec to 35
%1e-110100
> prec(55) # we can see what's happened, but let's see better
%0.0000000000000000000000000000000000000000000000000001
and we see that the apparently strange error is actually caused by one
bit in the computer's (binary) representation of the result. Not at all
strange...
Principal value ranges (PVRs)
-----------------------------
The PVRs are defined for the inverse trigonometric functions as follows:
asin: if u + iv = asin(z), then
-PI/2 <= u <= PI/2 if v >= 0
-PI/2 < u < PI/2 if v < 0
acos: if u + iv = acos(z), then
0 <= u <= PI if v >= 0
0 < u < PI if v < 0
atan: if u + iv = atan(z), then
-PI/2 < u < PI/2
Additional assignment operators
-------------------------------
As well as the standard = operator, icalc comes with some others both to
improve readability and efficiency of functions. They are:
a += b equivalent to a = a+b
a -= b equivalent to a = a-b
a *= b equivalent to a = a*b
a /= b equivalent to a = a/b
It is faster to use these operators rather than the explicit operations.
C: The ++ and -- operators are not included, since the evaluation method
C: cannot handle pre- and post- operations rigorously.
Relational operators
--------------------
These operators allow comparisons between two values. Of course, the set of
complex numbers has no logical ordering; all relational comparisons are
done with real parts only, but equivalence comparisons are done on both
real and imaginary parts. This system allows for people who are writing
real-number applications to once again completely ignore complex numbers.
To use relational operators on imaginary parts, you must use Im().
Relational operators are used in BOOLEAN expressions; these expressions can
take one of two values: TRUE (1.0) and FALSE (0.0). In the table below, a and
b represent numerical expressions, and E and F represent boolean expressions.
BOOLEAN EXPRESSION TRUE if and only if
a == b a has same value as b
a != b a has different value to b
a > b value of a greater than that of b
a >= b value of a greater than or equal to that of b
a < b value of a less than that of b
a <= b value of a less than or equal to that of b
E && F E is TRUE and F is TRUE
E || F E is TRUE or F is TRUE
Examples:
5 > 3 TRUE
2 != 2 FALSE
2 + 10i >= 20 + i FALSE (imaginary parts are ignored)
3 > 2i TRUE (2i has real part 0.0)
-3 > 2i FALSE
and as always, consider rounding errors when comparing:
PI == atan(1)*4 FALSE
The logical operators && and || use short-circuit evaluation. This means
that they only evaluate their right-hand argument if the result of the
operation is still undetermined. An example will clarify.
(5 > 3) && (6 < 8) Needs to evaluate E and F
E F
(5 > 8) && (6 < 8) E evaluates to FALSE, so expression is false,
E F and so F is not evaluated.
This has some important implications, for example if (boolean) expression F
contained an assignment expression, then it might not be evaluated
depending on expression E.
I have found that short-circuit evaluation is the most convenient way of
handling && and ||. Some languages don't guarantee even order of evaluation
(e.g. Pascal) and this can sometimes create harder to read programs.
One other operator is present in icalc, as a shorthand for if-then-else
statements, the ternary operator, '?:'. This has the form
E ? a : b
and reads as "if E is true then the value of the ternary operator is a,
otherwise it's b". Only one of a or b is evaluated. An example will clarify:
(x >= 2) ? (x -= 1) : (x = 100)
means "If x is greater than 2, then subtract one from x, otherwise assign the
value 100 to x".
Numerical expressions can stand in for boolean expressions. A numerical
expression has boolean value TRUE if its real part is non-zero, and FALSE
if it is zero. So, for example, if x is purely real,
y = (x ? 1 : 0) (*)
is equivalent to
y = (x != 0 ? 1 : 0) (**)
and assigns 1 to y if x is non-zero, and 0 to y if x is zero.
Do remember that imaginary parts are ignored in comparisons. In x were
purely imaginary, (*) and (**) would assign 1 and 0 respectively to y (i.e.
they would behave differently).
Arrays
------
icalc has simple one-dimensional arrays, which can only be declared outside
function definitions. Array elements are referenced using square brackets,
and the first element by default has index 1, but this can be changed.
C: This is in contrast to C, where indices start at zero. The system used in
C: icalc is more suited to many mathematical applications.
To create an array a, use the statement
array a[dimension] # creates cleared array
The dimension should be a positive integer. It may be an expression, which
will have its imaginary part discarded, and rounded DOWN to an integer.
All elements initially hold the value zero. You may optionally pre-assign
the array at creation by using
array a[dimension] = { expr1, expr2, ... exprP }
This second form allows pre-initialization of elements 1 to P;
P must be <= dimension (so can partially initialise array).
Arrays are referenced by a[expr], the index being calculated as the
real part of <expr> rounded to the NEAREST integer.
NB: in version 2.0, the index was calculated as the floor of the real
NB: value.
Range checking is performed, and user will be informed of invalid array
references.
As already mentioned, arrays must be created at the top level, ie. NOT in
function definitions. In this version, there are NO local (to function)
arrays.
There are a few builtin functions to help you manuipulate arrays:
arraybase(n) index n now denotes 1st element of all arrays
sizeof(a) returns number of elements in a,
resize(a,n) resizes a to n elements,
display(a) prints a list of elements in a.
The latter two functions return a value 1 by convention. If you resize()
an array to make it larger, the new elements created will NOT be set to
zero.
The arraybase routine lets you change how arrays are indexed. As already
stated, the first element by default has index 1. Using arraybase, any
non-negative integer may be used (primarily zero, as in C). arraybase(n)
returns the old base, so you can write functions which set things up how
the like at the start, and restore them when they return. Also, the
constant ABASE holds the current setting, so functions can also use that
to handle whatever preference is set. arraybase returns the value -1 if
given an invalid argument.
In the example scripts contained in the distribution of icalc, I have
NOT provided for array bases other than 1, except in zroots.ic, where
the routines change and restore the arraybase to suit their needs. If
you wish to use the other routines with a different base, you will have
to modify them using one of the above methods (I didn't want to confuse
the examples with excess baggage).
Here is a small example session to demonstrate some features:
> array a[3] = { 1, 2, 3 }
> display(a)
a[1] = 1
a[2] = 2
a[3] = 3
1
> a[2] = 12
12
> a[3] < a[2] ? x = 3 : x = 2
3
> sizeof(a)
3
> resize(a,4) # a[4] will contain garbage
1
> a[4] = 4
4
> display(a)
a[1] = 1
a[2] = 12
a[3] = 3
a[4] = 4
1
> resize(a,2)
1
> display(a)
a[1] = 1
a[2] = 12
Blocks
------
Blocks are a way to group expressions so they are treated as one big
expression. They have the form
{ expr1; expr2; ... exprN; }
and the value of a block is the value of its last expression, exprN. The
expressions may also be separated by newlines (as many as you like)instead.
Blocks can have as little as one expression, which is sometimes useful for
easier-to-read source code.
Examples:
> { a=3;b=2;a*b;}
6
> {
> a = 2
> b = 3
> c = 4; d = 5
> a*b*c*d
> }
120
Blocks are principally of use with control-flow constructs, and further
(more useful) examples are given below.
Control-flow constructs
-----------------------
A number of control-flow constructs are available, closely modelled on those
of the C language.
C: C is a free-form language, and spacing can appear anywhere in C source.
C: icalc however, is free-form only per-line, due to its interactive nature;
C: icalc must be able to determine when an expression stops, so that it knows
C: when to print a result. Care should be taken to follow icalc's conventions.
Where newlines are shown, they are optional; where they are not shown, they
are not permitted, and will either cause a syntax error, or incorrect
behaviour. All horizontal spacing is optional, and used for clarity.
Below, Bexpr denotes a boolean expression, Texpr is the expression
evaluated if Bexpr is TRUE, and Fexpr the expression evaluated if Bexpr is
FALSE.
if-else
-------
A construct common to almost all programming languages is the if-statement.
In icalc, it has the form
if (Bexpr)
Texpr
or with an else-clause,
if (Bexpr)
Texpr else
Fexpr
Note that 'else' keyword must be on same line as Texpr. If it were allowed
on the next line, non-else-clause if-statements would cause problems in
interactive use. It sometimes looks ungainly, but blocks may be used to
spread it over lines, as shown below.
Some examples:
if (a == 2)
b = a+1
if (a == 2)
b = a+1 else b = a-1
if (a == 2) {
b = a+1
} else {
b = a-1
}
while-loop
----------
This is an iteration construct, of the form
while (Bexpr)
Texpr
and reads as "while the boolean expression Bexpr evaluates to TRUE, evaluate
Texpr". The following example calculates n!, where n is an integer:
f = 1; n = 10; # initialize f, n
while (n > 0) # n not yet zero, so still work to do
{
f *= n # multiply f by n
n -= 1 # decrement n
}
# f now holds value of 10!
do-while-loop
-------------
This is another iteration construct, closely related to a while-loop, with
the distinction that the loop-test (the boolean expression governing the
loop's operation) is performed AFTER the body of the loop. It has the form
do
Texpr
while (Bexpr)
Texpr is evaluated first; then, if Bexpr is TRUE, Texpr is re-evaluated and
Bexpr tested again; and so on...
Whereas with a while-loop, the Texpr may never be evaluated (if Bexpr
evaluates to FALSE initially), in a do-while-loop, Texpr is always
evaluated at least once.
An example -- sin() applied five times to a value x:
n = 5; s = x # n is number of times to apply sin()
do {
s = sin(s) # apply sin()
n -= 1 # decrement n
} while (n >= 1) # more to do?
# s has value sin(sin(sin(sin(sin(x))))) (sin 5 times)
for-loop
--------
The for-loop in icalc is like that of the C language, and has the form
for (initexpr; condexpr; incexpr)
expr
initexpr, condexpr and incexpr are all optional (but semi-colons ; are not).
The for loop is a shorthand for
initexpr
while (condexpr)
{
expr
incexpr
}
Example:
array a[100]
for (n = sizeof(a); n > 0; n -= 1)
a[n] = 1
# all elements of a are 1.
C: icalc's for-loop is not as flexible as C's; there is no comma operator,
C: and initexpr, condexpr and incexpr can only be simple (not blocks).
Function definitions
--------------------
The User Guide explains how to define simple functions. As mentioned there,
you can use a block as the body of a function (e.g. rec2pol definition).
There are more sophisticated facilities available, which provide a similar
functionality to that of C. However, for a number of reasons, the syntax
used in icalc differs widely from C's.
Function parameters may be one of four types:
VALUE: These are the normal parameters that we've been using
all along.
POINTER: These are like variable parameters in Pascal.
Denoted in parameter list by *.
ARRAY: These are passed by reference, NOT value, as in C
Denoted by @.
EXPR: Strange one this. It's like a pointer to a function,
but more/less flexible depending on how you look
at it. Example below will clarify.
Denoted by ~.
Note that, although a function cannot have local arrays, the array
reference parameter IS local, and completely independent of arrays defined
globally.
Functions may also have local variables, introduced by a statement
local v1,v2,v3,...,vN
within the function body. There can be as many local declarations as you
like. All local variables behave like VALUE variables - there are no local
pointers or arrays as yet.
C: local declarations apply to the function they appear in; there are no
C: block-local variables.
The following small suite of functions demostrate the ideas above.
# swaps values in variables a and b.
func swap(*a,*b) = { # a and b are pointers
local tmp # only expressions in swap() know about tmp
tmp = a
a = b
b = tmp
}
x = 5; y = 7
swap(x,y) # x now holds 7, y holds 5
# nexpr is an expression in (global) variable n
# a is an (arbitrary) array.
# fill evaluates Nexpr for every index n of array a, and places
# result in a[n].
func fill(@a, ~nexpr) = {
for (n = sizeof(a); n > 0; n -= 1)
a[n] = nexpr; # just referencing nexpr causes
# it to be evaluated.
}
The return value of a function is the value of the last expression evaluated.
Sometimes it's inconvenient for this to be the case, so the return statement
is provided. A line of the form
return a
encountered anywhere during function evaluation will end the function call,
returning the value of a.
I'm sure you will agree that these mechanisms allow many sophisticated
operations to be performed by user-functions. The examples in this document
are simple-minded for the most part, to give you the bare facts about what
can be done. I have included many sample script-files containing one or more
function definitions which are useful to a varying degree, and in some
cases add very powerful extensions to icalc.
Script files
------------
The script files include:
array.ic - utility routines for arrays
arraytest.ic - tests routines defined in array.ic
bern.ic - computes Bernoulli numbers
cheby.ic - computes coefficients for Chebychev polynomials
gamma.ic - gamma and related functions
gl.ic - integration by 10-pt Gauss-Legendre quadrature
loop.ic - demonstrates all looping constructs
poly.ic - polynomial support routines
root.ic - root-finding of functions
sort.ic - routine to perform Shell sort on arrays
trig.ic - some rarely used trig-functions
zroots.ic - find all roots of a polynomial (real & complex)
polint.ic -
trapzd.ic |
qtrap.ic |- numerical integration routines
qsimp.ic | (see Integration.notes in Scripts directory).
qromb.ic -
Undocumented commands
---------------------
There is a 'clear' command, which deletes from memory a variable, array or
function. However, care MUST be taken, since icalc does not check that the
object being deleted is not referenced elsewhere (in a function). If it is
referenced after it has been deleted, the system will more than likely
crash. The command was only really added to delete large arrays which take
up a lot of memory. You have been warned...
clear <symbol>
Caveats
-------
Assignment
----------
When assigning variables within another expression, it is safer to
enclose the assignment in parentheses, to ensure that the expression
is evaluated correctly.
Defining functions in terms of other functions
----------------------------------------------
A function is stored internally as a structure, and a reference to
a function, f, from another, g, creates a pointer to the structure
for f. Thus, changing f changes g also.
An example will illuminate this:
> func f(x) = x*x
> func g(x) = f(2*x)
> f(4)
16
> g(4)
64
> func f(x) = sqrt(x) # changes behaviour of g
> f(4)
2
> g(4)
2.82842712
Therefore, you cannot use function definitions as a kind of function
assignment.
Comparisons
-----------
Due to the inexactness of computer calculations, you must be careful
when using comparison operators. Again, an example will illuminate:
> j = 12*PI
37.69911184
> while j != 0 do {print(j); j = j-PI;}
37.69911184
34.55751919
:
3.14159265
-3.5527136e-15 <--- not quite zero
-3.14159265
:
etc.
The arrow points to the position where, because of rounding errors,
j is not exactly zero, and the loop never terminates, since the
condition j != 0 is always satisfied.
There are two ways to get round problems like this. You can use a >=
operator, or, if comparison is with (theoretically) integral values,
the int() function, which rounds its argument to the NEAREST integer.
Questions
---------
I've tried to cover everything in a clear and precise way, but almost
certainly have failed in some areas. If you have any questions about icalc,
write to me and I'll respond as soon as I can. Addresses are in the User
Guide.
Also, if you write any scripts which you feel other users may find useful
(for example, routines applying to physics or astronomy calculations)
please send them to me for inclusion with the next release of icalc.
Enjoy,
Martin Scott.